#ifndef EMITTERSTATE_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define EMITTERSTATE_H_62B23520_7C8E_11DE_8A39_0800200C9A66

#if defined(_MSC_VER) ||                                            \
    (defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || \
     (__GNUC__ >= 4)) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif

#include "setting.h"
#include "yaml-cpp/emitterdef.h"
#include "yaml-cpp/emittermanip.h"

#include <cassert>
#include <memory>
#include <stack>
#include <stdexcept>
#include <vector>

namespace YAML {
struct FmtScope {
    enum value { Local, Global };
};
struct GroupType {
    enum value { NoType, Seq, Map };
};
struct FlowType {
    enum value { NoType, Flow, Block };
};

class EmitterState {
public:
    EmitterState();
    ~EmitterState();

    // basic state checking
    bool good() const { return m_isGood; }
    const std::string GetLastError() const { return m_lastError; }
    void SetError(const std::string& error) {
        m_isGood = false;
        m_lastError = error;
    }

    // node handling
    void SetAnchor();
    void SetAlias();
    void SetTag();
    void SetNonContent();
    void SetLongKey();
    void ForceFlow();
    void StartedDoc();
    void EndedDoc();
    void StartedScalar();
    void StartedGroup(GroupType::value type);
    void EndedGroup(GroupType::value type);

    EmitterNodeType::value NextGroupType(GroupType::value type) const;
    EmitterNodeType::value CurGroupNodeType() const;

    GroupType::value CurGroupType() const;
    FlowType::value CurGroupFlowType() const;
    std::size_t CurGroupIndent() const;
    std::size_t CurGroupChildCount() const;
    bool CurGroupLongKey() const;

    std::size_t LastIndent() const;
    std::size_t CurIndent() const { return m_curIndent; }
    bool HasAnchor() const { return m_hasAnchor; }
    bool HasAlias() const { return m_hasAlias; }
    bool HasTag() const { return m_hasTag; }
    bool HasBegunNode() const {
        return m_hasAnchor || m_hasTag || m_hasNonContent;
    }
    bool HasBegunContent() const { return m_hasAnchor || m_hasTag; }

    void ClearModifiedSettings();
    void RestoreGlobalModifiedSettings();

    // formatters
    void SetLocalValue(EMITTER_MANIP value);

    bool SetOutputCharset(EMITTER_MANIP value, FmtScope::value scope);
    EMITTER_MANIP GetOutputCharset() const { return m_charset.get(); }

    bool SetStringFormat(EMITTER_MANIP value, FmtScope::value scope);
    EMITTER_MANIP GetStringFormat() const { return m_strFmt.get(); }

    bool SetBoolFormat(EMITTER_MANIP value, FmtScope::value scope);
    EMITTER_MANIP GetBoolFormat() const { return m_boolFmt.get(); }

    bool SetBoolLengthFormat(EMITTER_MANIP value, FmtScope::value scope);
    EMITTER_MANIP GetBoolLengthFormat() const { return m_boolLengthFmt.get(); }

    bool SetBoolCaseFormat(EMITTER_MANIP value, FmtScope::value scope);
    EMITTER_MANIP GetBoolCaseFormat() const { return m_boolCaseFmt.get(); }

    bool SetNullFormat(EMITTER_MANIP value, FmtScope::value scope);
    EMITTER_MANIP GetNullFormat() const { return m_nullFmt.get(); }

    bool SetIntFormat(EMITTER_MANIP value, FmtScope::value scope);
    EMITTER_MANIP GetIntFormat() const { return m_intFmt.get(); }

    bool SetIndent(std::size_t value, FmtScope::value scope);
    std::size_t GetIndent() const { return m_indent.get(); }

    bool SetPreCommentIndent(std::size_t value, FmtScope::value scope);
    std::size_t GetPreCommentIndent() const { return m_preCommentIndent.get(); }
    bool SetPostCommentIndent(std::size_t value, FmtScope::value scope);
    std::size_t GetPostCommentIndent() const {
        return m_postCommentIndent.get();
    }

    bool SetFlowType(GroupType::value groupType, EMITTER_MANIP value,
                     FmtScope::value scope);
    EMITTER_MANIP GetFlowType(GroupType::value groupType) const;

    bool SetMapKeyFormat(EMITTER_MANIP value, FmtScope::value scope);
    EMITTER_MANIP GetMapKeyFormat() const { return m_mapKeyFmt.get(); }

    bool SetFloatPrecision(std::size_t value, FmtScope::value scope);
    std::size_t GetFloatPrecision() const { return m_floatPrecision.get(); }
    bool SetDoublePrecision(std::size_t value, FmtScope::value scope);
    std::size_t GetDoublePrecision() const { return m_doublePrecision.get(); }

private:
    template <typename T>
    void _Set(Setting<T>& fmt, T value, FmtScope::value scope);

    void StartedNode();

private:
    // basic state ok?
    bool m_isGood;
    std::string m_lastError;

    // other state
    Setting<EMITTER_MANIP> m_charset;
    Setting<EMITTER_MANIP> m_strFmt;
    Setting<EMITTER_MANIP> m_boolFmt;
    Setting<EMITTER_MANIP> m_boolLengthFmt;
    Setting<EMITTER_MANIP> m_boolCaseFmt;
    Setting<EMITTER_MANIP> m_nullFmt;
    Setting<EMITTER_MANIP> m_intFmt;
    Setting<std::size_t> m_indent;
    Setting<std::size_t> m_preCommentIndent, m_postCommentIndent;
    Setting<EMITTER_MANIP> m_seqFmt;
    Setting<EMITTER_MANIP> m_mapFmt;
    Setting<EMITTER_MANIP> m_mapKeyFmt;
    Setting<std::size_t> m_floatPrecision;
    Setting<std::size_t> m_doublePrecision;

    SettingChanges m_modifiedSettings;
    SettingChanges m_globalModifiedSettings;

    struct Group {
        explicit Group(GroupType::value type_)
            : type(type_), flowType{}, indent(0), childCount(0),
              longKey(false), modifiedSettings{} {}

        GroupType::value type;
        FlowType::value flowType;
        std::size_t indent;
        std::size_t childCount;
        bool longKey;

        SettingChanges modifiedSettings;

        EmitterNodeType::value NodeType() const {
            if (type == GroupType::Seq) {
                if (flowType == FlowType::Flow)
                    return EmitterNodeType::FlowSeq;
                else
                    return EmitterNodeType::BlockSeq;
            }
            else {
                if (flowType == FlowType::Flow)
                    return EmitterNodeType::FlowMap;
                else
                    return EmitterNodeType::BlockMap;
            }

            // can't get here
            assert(false);
            return EmitterNodeType::NoType;
        }
    };

    std::vector<std::unique_ptr<Group>> m_groups;
    std::size_t m_curIndent;
    bool m_hasAnchor;
    bool m_hasAlias;
    bool m_hasTag;
    bool m_hasNonContent;
    std::size_t m_docCount;
};

template <typename T>
void EmitterState::_Set(Setting<T>& fmt, T value, FmtScope::value scope) {
    switch (scope) {
    case FmtScope::Local:
        m_modifiedSettings.push(fmt.set(value));
        break;
    case FmtScope::Global:
        fmt.set(value);
        m_globalModifiedSettings.push(
            fmt.set(value)); // this pushes an identity set, so when we restore,
        // it restores to the value here, and not the previous one
        break;
    default:
        assert(false);
    }
}
} // namespace YAML

#endif // EMITTERSTATE_H_62B23520_7C8E_11DE_8A39_0800200C9A66
